package es

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"strings"

	"lib/utils"
)

type Metadata struct {
	Name    string
	Version int
	Size    int64
	Hash    string
}

func urlGet(name string, version int) string {
	return fmt.Sprintf("http://%s/metadata/objects/%s_%d/_source", utils.EsServer(), name, version)
}

func getMetadata(name string, versionId int) (meta Metadata, e error) {
	r, e := http.Get(urlGet(name, versionId))
	if e != nil {
		log.Println("Get metadata failed: ", e)
		return
	}
	if r.StatusCode != http.StatusOK {
		e = fmt.Errorf("fail to get %s_%d: %d", name, versionId, r.StatusCode)
		log.Println(e)
		return
	}
	result, _ := ioutil.ReadAll(r.Body)
	json.Unmarshal(result, &meta)
	return
}

type hit struct {
	Source Metadata `json:"_source"`
}

type searchResult struct {
	Hits struct {
		Total int
		Hits  []hit
	}
}

func urlSearchName(name string) string {
	// sort by version descending order
	return fmt.Sprintf("http://%s/metadata/_search?q=name:%s&size=1&sort=version:desc",
		utils.EsServer(), name)
}

func SearchLatestVersion(name string) (meta Metadata, e error) {
	r, e := http.Get(urlSearchName(name))
	if e != nil {
		log.Println("Search failed: ", e)
		return
	}
	if r.StatusCode != http.StatusOK {
		e = fmt.Errorf("Fail to search latest metadata: %d", r.StatusCode)
		log.Println(e)
		return
	}
	result, _ := ioutil.ReadAll(r.Body)
	var sr searchResult
	json.Unmarshal(result, &sr)
	if len(sr.Hits.Hits) != 0 {
		// first is latest version
		meta = sr.Hits.Hits[0].Source
	}
	return
}

func GetMetadata(name string, version int) (Metadata, error) {
	if version == 0 {
		return SearchLatestVersion(name)
	}

	return getMetadata(name, version)
}

func urlPut(name string, version int) string {
	return fmt.Sprintf("http://%s/metadata/objects/%s_%d?op_type=create", utils.EsServer(), name, version)

}

func PutMetadata(name string, version int, size int64, hash string) error {
	doc := fmt.Sprintf(`{"name":"%s", "version":%d, "size":%d, "hash":"%s"}`,
		name, version, size, hash)
	client := http.Client{}
	request, _ := http.NewRequest("PUT", urlPut(name, version), strings.NewReader(doc))
	r, e := client.Do(request)

	if e != nil {
		log.Println("Put metadata failed: ", e)
		return e
	}
	if r.StatusCode == http.StatusConflict {
		log.Println("Push metadata conflict, try again")
		return PutMetadata(name, version, size, hash)
	}
	if r.StatusCode != http.StatusCreated {
		result, _ := ioutil.ReadAll(r.Body)
		e = fmt.Errorf("Fail to put metadata: %d %s", r.StatusCode, string(result))
		log.Println(e)
		return e
	}
	return nil
}

func AddVersion(name string, size int64, hash string) error {
	version, e := SearchLatestVersion(name)
	if e != nil {
		log.Println(e)
		return e
	}
	return PutMetadata(name, version.Version+1, size, hash)
}

func urlSearchAll(name string, from, size int) string {
	url := fmt.Sprintf("http://%s/metadata/_search?sort=name,version&from=%d&size=%d",
		utils.EsServer(), from, size)
	if name != "" {
		url += "&q=name:" + name
	}
	return url
}

func SearchAllVersion(name string, from, size int) ([]Metadata, error) {
	r, e := http.Get(urlSearchAll(name, from, size))
	if e != nil {
		log.Println("Search all failed: ", e)
		return nil, e
	}

	metas := make([]Metadata, 0)
	result, _ := ioutil.ReadAll(r.Body)
	var sr searchResult
	json.Unmarshal(result, &sr)
	for i := range sr.Hits.Hits {
		metas = append(metas, sr.Hits.Hits[i].Source)
	}
	return metas, nil
}
